/*******************************************************************
程序：tupingGame
文件：main.c
功能：打字游戏
日期：2023.10.2
作者：机电与信息工程学院 21电子01
********************************************************************/
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <stdio.h>

//预编译命令
#pragma comment(lib,"winmm.lib")

#define TIMER1     1                //定时器编号
#define FINISHMAX  12               //通关需要完成的个数

//创建对象
HFONT defaultFont = CreateFont(20, 0, 0, 0,                                                                  //默认字体（字体对象）
	FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, 
	OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, TEXT("黑体"));
HFONT bigFont = CreateFont(90, 0, 0, 0,                                                                      //大字体（字体对象斜体 加粗
	FW_BOLD, TRUE, FALSE, FALSE, DEFAULT_CHARSET,
	OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, TEXT("Times New Roman"));
HBITMAP birdImage = (HBITMAP)LoadImage(NULL,"./resource/bird.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);     //bmp格式的位图
HBITMAP clockImage = (HBITMAP)LoadImage(NULL, "./resource/clock.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);  
HBITMAP faceImage = (HBITMAP)LoadImage(NULL, "./resource/facephoto.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);



//变量声明
char objectStr[30] = { '\0'}; //定义目标字符串
int gScore = 0;                 //初始得分为0
char numShowStr[60] = { 0 };    //用于数字显示的字符串
char musicStr[30] = { 0 };      //用于播放音乐的字符串
int startSecond = 0;         //系统运行的秒数
int gameStartFlag = 0;       //游戏是否开始的标志位
int countDown = 30;          //倒计时
int finishNum = 0;           //完成字符串的个数
int Score[FINISHMAX + 10] = { 10,10,20,20,50,50,100,100,200,200,500,500}; //得分数组(越往后得分越高)
int countDownSecond[FINISHMAX + 10] = {30,25,20,20,28,15,15,15,15,15,15,15};//倒计时数组(越往后倒计时越小,这里可以调难度)

//框架函数声明
BOOL InitWindow(HINSTANCE hInstance, int nCmdShow);
LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

//自定义函数声明
BOOL judgeCharToStr(char aChar, char* pStr, int Slen);   //判断并处理输入的字符,成功就从字符串中删去，然后返回TRUE
BOOL ifFinishStr(char* pStr);                            //判断字符串是否打完，打完返回TRUE
void createObjectStr(char* pStr);                        //产生新的随机字符串
BOOL ifGameOver(int Num);                                //判断游戏是否通关
void musicPlay(int num);                                 //播放音乐


int WINAPI WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	PSTR szCmdLine,
	int iCmdShow)
{
	MSG msg;

	if (!InitWindow(hInstance, iCmdShow))
		return FALSE;

	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);        
		DispatchMessage(&msg);
	}

	return msg.wParam;
}



static BOOL InitWindow(HINSTANCE hInstance, int iCmdShow)
{
	static TCHAR szAppName[] = TEXT("Keyboard");
	HWND hwnd;
	WNDCLASS wc;

	wc.style = CS_VREDRAW | CS_HREDRAW;
	wc.lpfnWndProc = WinProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);  //在这里我们将窗口背景设为黑色
	wc.lpszMenuName = NULL;
	wc.lpszClassName = szAppName;

	if (!RegisterClass(&wc))
	{
		MessageBox(NULL, TEXT("注册窗口类失败！"), szAppName,
			MB_ICONERROR);
		return 0;
	}

	hwnd = CreateWindow(szAppName,
		TEXT("typingGame"),
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		NULL,
		NULL,
		hInstance,
		NULL);

	if (!hwnd) return FALSE;

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);
	return TRUE;
}


//窗口函数
LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC           hdc;
	HDC           mdc;
	PAINTSTRUCT   ps;
	RECT          rect;
	TEXTMETRIC    textInform;                      //字体度量信息
	static char   aChar = '0';                     //字符键
	int i;

	switch (message)
	{
		case WM_CREATE:   //窗口创建时的整个初始化操作
			SetTimer(hWnd, TIMER1, 1000, NULL);		                                //创建定时器(1000ms)
			finishNum = 0;							                                //初始化字符串完成个数
			gScore = 0;								                                //初始化得分
			countDown = countDownSecond[finishNum];                                 //初始化倒计时
			createObjectStr(objectStr);                                             //产生随机数组
			PlaySound("./resource/bkmusic.wav", NULL, SND_FILENAME | SND_ASYNC);    //播放背景音乐
			return 0;

		case WM_KEYDOWN:    //按键按下的处理，在这里处理相关控制
			switch (wParam)
			{
				case VK_RETURN:   //回车键
					gameStartFlag = 1;
					PlaySound("./resource/begin.wav", NULL, SND_FILENAME | SND_ASYNC);
					break;

				case VK_ESCAPE:    //ESC键
					if (MessageBox(hWnd, "终止游戏并退出？","是否退出？",MB_YESNO) == IDYES)
					{
						SendMessage(hWnd,WM_CLOSE,0,0);
					}
					break;

				default:
					break;
			}

			return 0;

		case WM_CHAR:
			aChar = wParam;

			if (gameStartFlag == 1)
			{
				if (judgeCharToStr(aChar, objectStr, strlen(objectStr)))
				{
					if (ifFinishStr(objectStr))
					{
						//播放音乐
						musicPlay(finishNum);
						//处理得分
						gScore += Score[finishNum];
						//更新倒计时
						finishNum++;                          
						countDown = countDownSecond[finishNum];
						//判断是否通关
						if (ifGameOver(finishNum))
						{
							InvalidateRect(hWnd, NULL, TRUE);
							KillTimer(hWnd, TIMER1);  //销毁定时器
							sprintf(numShowStr, "恭喜你通关了,你的得分是%d分，所用的时间是%d分%d秒，强无敌！", gScore, startSecond / 60, startSecond % 60);
							if (MessageBox(hWnd, numShowStr, "通关大吉", MB_OK) == IDOK)
							{
								SendMessage(hWnd, WM_CLOSE, 0, 0);
							}
							break;
						}
						createObjectStr(objectStr);
					}
					else
					{
						gScore += (finishNum + 1);
					}
				}
			}
			
			//刷新重绘消息
			InvalidateRect(hWnd,NULL,TRUE);
			return 0;
    
		//窗口重绘消息（进行窗口重新绘制）
		case WM_PAINT:  
			hdc = BeginPaint(hWnd, &ps);//取得设备环境句柄
			mdc = CreateCompatibleDC(hdc); ///创建一个与指定设备兼容的内存设备上下文环境.用于显示图片
			//取得窗口客户区矩形
			GetClientRect(hWnd, &rect);

			if (gameStartFlag == 1)
			{
				//设置文字颜色
				SetBkColor(hdc, RGB(0, 0, 0));
				SetTextColor(hdc, RGB(0, 255, 0));
				// 设置字体为大字体 
				SelectObject(hdc, bigFont);
				//输出文字(显示当前要打的字符串)
				DrawText(hdc, objectStr, strlen(objectStr), &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
				//设置字体为默认字体,并获取字体信息 
				SelectObject(hdc, defaultFont);
				GetTextMetrics(hdc, &textInform);
				//显示得分
				SetBkColor(hdc, RGB(0, 0, 0));
				SetTextColor(hdc, RGB(0, 255, 0));
				SelectObject(mdc, birdImage);    //加载图片到设备
				BitBlt(hdc, rect.right / 80, rect.bottom / 80, 100, 100, mdc, 0, 0, SRCCOPY);
				TextOut(hdc, rect.right / 13, rect.bottom / 30, "您当前的得分是:", strlen("您当前的得分是:"));
				sprintf(numShowStr, "%d", gScore);
				SetTextColor(hdc, RGB(0, 255, 0));
				TextOut(hdc, rect.right / 13 + strlen("您当前的得分是:") * textInform.tmAveCharWidth, rect.bottom / 30, numShowStr, strlen(numShowStr));

				//显示倒计时
				SetTextColor(hdc, RGB(255, 0, 0));
				sprintf(numShowStr, "倒计时: %d s", countDown);
				TextOut(hdc, rect.right * (1 / 13.0), rect.bottom * (2 / 25.0), numShowStr, strlen(numShowStr));


				//显示时间
				SelectObject(mdc, clockImage);    //加载图片到设备
				BitBlt(hdc, rect.right * (13 / 18.0), rect.bottom * (18 / 20.0), 60, 60, mdc, 0, 0, SRCCOPY);
				SetTextColor(hdc, RGB(0, 255, 0));
				TextOut(hdc, rect.right * (10 / 13.0), rect.bottom * (18.5 / 20.0), "您所用的时间为:", strlen("您所用的时间为:"));
				sprintf(numShowStr, "%d min %d s", startSecond / 60, startSecond % 60);
				SetTextColor(hdc, RGB(255, 0, 0));
				TextOut(hdc, rect.right * (10 / 13.0) + strlen("您所用的时间为:") * textInform.tmAveCharWidth + 5, rect.bottom * (18.5 / 20.0), numShowStr, strlen(numShowStr));
			}
			else
			{
				//设置文字颜色
				SetBkColor(hdc, RGB(0, 0, 0));
				SetTextColor(hdc, RGB(0, 0, 255));
			    SelectObject(mdc, faceImage);    //加载图片到设备
				BitBlt(hdc, rect.right* (6.9 / 18.0), rect.bottom* (1 / 20.0), 500, 500, mdc, 0, 0, SRCCOPY);
				// 设置字体为大字体 
				SelectObject(hdc, bigFont);
				TextOut(hdc, rect.right* (5.0 / 18.0), rect.bottom* (12 / 20.0), "疯狂打字游戏 (^_^)", strlen("疯狂打字游戏 (^_^)"));
				SelectObject(hdc, defaultFont);
				TextOut(hdc, rect.right* (10 / 13.0), rect.bottom* (18.5 / 20.0), "按下Enter键开始游戏......", strlen("按下Enter键开始游戏......"));
			}
			
			DeleteObject(mdc);  //删除用于图片显示的兼容性内存设备 
			EndPaint(hWnd, &ps);//释放资源
			return 0;
	
		case WM_TIMER:
			if (gameStartFlag == 1)
			{
				startSecond++; 
				countDown--;
				if (startSecond >= 3600)//最大定时时间60min,防止溢出
					startSecond = 3600;
				if (countDown <= 0 || startSecond == 3600)
				{
					KillTimer(hWnd, TIMER1);  //销毁定时器
					sprintf(numShowStr, "你打字有点慢哟，游戏结束了,你的得分是%d分，继续加油！", gScore);
					if (MessageBox(hWnd, numShowStr, "游戏结束", MB_OK) == IDOK)
					{
						SendMessage(hWnd, WM_CLOSE, 0, 0);
					}
					break;
				}
			}
			
			InvalidateRect(hWnd, NULL, TRUE);
			return 0;
		
		//窗口销毁处理
		case WM_DESTROY:
			KillTimer(hWnd, TIMER1);  //销毁定时器
			PostQuitMessage(0);
			return 0;
	}

	return DefWindowProc(hWnd, message, wParam, lParam);

} //函数 WinProc 结束



/*
* function: 判断并处理输入的字符,成功就从字符串中删去，然后返回TRUE
* parameter:  @aChar  要处理的字符
*             @pStr   指向要进行匹配的目标数组的指针 
*             @Slen   目标数组的长度
* return: TRUE or FALSE
*/
BOOL judgeCharToStr(char aChar, char* pStr,int Slen)
{
	int i = 0;
	for (i = 0; i < Slen; i++)
	{
		if (*(pStr + i) == aChar)
		{
			memmove(pStr + i, pStr + i + 1, Slen - i);
			return TRUE;
		}
	}
	return FALSE;
}


/*
* function: 判断字符串是否打完，打完返回TRUE
* parameter:  @pStr   指向要进行匹配的目标数组的指针
* return: TRUE or FALSE
*/
BOOL ifFinishStr(char* pStr)
{
	if (strlen(pStr) == 0)
		return TRUE;
	else
		return FALSE;
}

/*
* function: 随机更新目标数组的内容
* parameter:  @pStr   指向要进行匹配的目标数组的指针
* return: 无
*/
void createObjectStr(char* pStr)
{
	int i = 0;
	int strmax = 6;
	SYSTEMTIME    tmSeed;
	//获取最机数种子
	GetLocalTime(&tmSeed);
	srand(tmSeed.wSecond);
	//字符串长度随机（1-10）,不能出现0！
	strmax = 1 + rand() % 11;
	//更新种子
	GetLocalTime(&tmSeed);
	srand(tmSeed.wSecond);
    //字符串内容随机
	for (i = 0; i < strmax; i++)
	{
		pStr[i] = 'A' + rand() % ('z' - 'A' + 1);
	}
	pStr[strmax] = '\0';
}


/*
* function: 判断游戏是否结束，结束返回TRUE
* parameter:  @Num   打完的字符串的个数
* return: TRUE or FALSE
*/
BOOL ifGameOver(int Num)
{
	if (Num >= FINISHMAX)
		return TRUE;
	else
		return FALSE;
}

/*
* function: 根据打完的个数选择相应的音效进行播放
* parameter:  @Num   打完的字符串的个数
* return: 无
*/
void musicPlay(int num)
{
	if (num == FINISHMAX - 1)
	{
		sprintf(musicStr, "./resource/victory.wav");
		PlaySound(musicStr, NULL, SND_FILENAME | SND_ASYNC);
	}
	else if (num <= 8)
	{
		sprintf(musicStr, "./resource/music%d.wav", finishNum);
		PlaySound(musicStr, NULL, SND_FILENAME | SND_ASYNC);
	}
	else if (num > 8 && num < FINISHMAX - 1)
	{
		sprintf(musicStr, "./resource/music8.wav", finishNum);
		PlaySound(musicStr, NULL, SND_FILENAME | SND_ASYNC);
	}
}